﻿using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;


namespace GuavaData
{
    /*

     <DDLs>
       <table type="add" name="[name]">
         <column name="[name]" key="true">
            <domain type="[type]"\>
         </column>            
         <column name="[name]" key="false">
            <domain type="[DATETIME]" />
         </column>            
       </table>
       <table type="rename" new=[newname] old=[oldname]/>
       <table type="delete" name=[name] />
         <column type="add" name=[name] table=[table]>
            <domain type=[domain] />
         </column>
       <column type="rename" new=[newname] old=[oldname] table=[table] />
       <column type="delete" name=[name] table=[table] />
       <element type="add"  value=[value] column=[column] table=[table] />
       <element type="rename" new=[newvalue] old=[oldvalue] column=[column] table=[table] />
       <element type="delete" value=[value] column=[column] table=[table] />
       <fk type="add" childColumn="[cols]" childCondition="[conds]" parentColumn="[cols]" parentCondition="conditions">
       <partition marker=[timestamp]/>
     </DDLs>
    */
    class SchemaController
    {
        
        XDocument xml;
        string docName;
        static string DEFAULT_DOC_NAME = "../../schema_log.xml";
        XDocument dbSchema;
        Int32 dbVersion;
        Boolean usingDBSchema=false;
        Boolean logging = true;
        Channel channel;

        public SchemaController(String path, String docName, Schema sch, Channel c, Boolean logging)
        {
            this.logging = logging;
            init(path, docName, sch, c);
        }

        public SchemaController(String path, Schema sch)
        {
            init(path, null, sch, null);
        }
        public SchemaController(String path, String docName, Schema sch, Channel c)
        {
            init(path, docName, sch, c);
        }

        public SchemaController(String path, String docName, Schema sch)
        {
            init(path, docName, sch, null);
        }

        private void init(String path, String file, Schema sch, Channel c){
            if (logging)
            {
                if (file == null)
                    this.docName = DEFAULT_DOC_NAME;
                else this.docName = file;
                channel = c;
                try
                {
                    this.xml = XDocument.Load(docName);
                }
                catch
                {
                    this.xml = new XDocument(new XElement("DDLs"));
                    TransactionToXml(sch.Transaction);
                    xml.Element("DDLs").Add(new XElement("version", "0"));
                    xml.Save(docName);
                }
            }
            this.xml = new XDocument(new XElement("DDLs"));
            TransactionToXml(sch.Transaction);
            xml.Element("DDLs").Add(new XElement("version", "0"));

        }

        public void RetriveDBSchema(Schema sch)
        {
            usingDBSchema = true;
            try
            {
                DBConnection.ExecuteNonQuery("CREATE TABLE DBVersion (Version int NOT NULL, XMLSchema text NULL, XMLChannel text NULL, CONSTRAINT pk_DBVersion PRIMARY KEY CLUSTERED (Version))");
            }
            catch{}
            //AddTable at = new AddTable("DBVersion");
            //at.AddColumn("Version", new Domain.Integer(), true);
            //at.AddColumn("XMLSchema", new Domain.String(), false);
            //AppData.c.PoseTransaction(new Transaction(at));
            
            DataSet ds = DBConnection.ExecuteQuery("select * from DBVersion where version=(select max(version) from dbversion)");
            DataTable dt = ds.Tables[0];
            if (dt.Rows.Count == 0)
            {
                XDocument dbx = new XDocument(new XElement("DDLs"));
                dbx.Element("DDLs").Add(xml.Element("DDLs").Element("version").ElementsBeforeSelf());

                string c = "NULL";
                if (channel != null)
                    c = channel.ToXDocument().ToString();

                DBConnection.ExecuteQuery("insert into DBversion (version, xmlschema, xmlchannel) values (0, '" + dbx.ToString() + "', '"+c+ "')");
                //TransactionFromXmlChanges(xml.Element("DDLs").Elements());
                dbVersion = 0;
                dbSchema = dbx;
            }
            else
            {
                dbVersion = (Int32) dt.Rows[0]["version"];
                dbSchema = XDocument.Parse( (string) dt.Rows[0]["xmlschema"]);
            }

        }

        public void TransactionToXml(Transaction t)
        {
            if (t.Contents == null)
                return;
            foreach (Command c in t.Contents)
            {
                LogOperator(c.root);
            }
            if(logging)
                xml.Save(docName);
        }

        public void LogOperator(Operator op)
        {
            if (op is AddTable)
            {
                writeDDL(CreateAddTableLog((AddTable)op));
            }
            else if (op is AlterTableRename)
            {
                AlterTableRename rt = (AlterTableRename) op;
                writeDDL(new XElement("table",
                    new XAttribute("type", "rename"),
                    new XAttribute("old", rt.OldName),
                    new XAttribute("new", rt.NewName)));
            }
            else if (op is DropTable)
            {
                DropTable dt = (DropTable)op;
                writeDDL(new XElement("table",
                    new XAttribute("type", "delete"),
                    new XAttribute("name", dt.Table)));
            }
            else if (op is AddColumn)
            {
                AddColumn ac = (AddColumn)op;
                writeDDL(new XElement("column",
                    new XAttribute("type", "add"),
                    new XAttribute("name", ac.Column),
                    new XAttribute("table", ac.Table),
                    ac.Domain.ToXElement()));
            }
            else if (op is AlterColumnRename)
            {
                AlterColumnRename rc = (AlterColumnRename)op;
                writeDDL(new XElement("column",
                    new XAttribute("type", "rename"),
                    new XAttribute("old", rc.OldName),
                    new XAttribute("new", rc.NewName),
                    new XAttribute("table", rc.Table)));
            }
            else if (op is DropColumn)
            {
                DropColumn dc = (DropColumn)op;
                writeDDL(new XElement("column",
                    new XAttribute("type", "delete"),
                    new XAttribute("name", dc.Column),
                    new XAttribute("table", dc.Table)));
            }
            else if (op is AddElement)
            {
                AddElement ae = (AddElement)op;
                writeDDL(new XElement("element",
                    new XAttribute("type", "add"),
                    new XAttribute("column", ae.Column),
                    new XAttribute("value", ae.ToString()),
                    new XAttribute("table", ae.Table)));
            }
            else if (op is AlterElementRename)
            {
                AlterElementRename re = (AlterElementRename)op;
                writeDDL(new XElement("element",
                    new XAttribute("type", "rename"),
                    new XAttribute("column", re.Column),
                    new XAttribute("old", re.OldName),
                    new XAttribute("new", re.NewName),
                    new XAttribute("table", re.Table)));
            }
            else if (op is DropElement)
            {
                DropElement de = (DropElement)op;
                writeDDL(new XElement("element",
                    new XAttribute("type", "delete"),
                    new XAttribute("column", de.Column),
                    new XAttribute("table", de.Table)));
            }
            else if (op is ForeignKey)
            {
                ForeignKey fk = (ForeignKey)op;
                writeDDL(CreateForeignKeyLog(fk));
            }
        }
        private XElement CreateAddTableLog(AddTable at)
        {
            XElement ate = new XElement("table", new XAttribute("type","add"), new XAttribute("name", at.Table));
            for (int i = 0; i < at.Columns.Count; i++)
            {

                XElement ce = new XElement("column", new XAttribute("name", at.Columns[i]), new XAttribute("key", at.Keys.Contains(at.Columns[i]).ToString()));
                ce.Add(at.Domains[i].ToXElement());
                ate.Add(ce);
            }
            return ate;
        }

        private XElement CreateForeignKeyLog(ForeignKey fk)
        {
            XElement fke = new XElement("fk", new XAttribute("type", "add"));
            string childCols = "";
            string childConds = "";
            string parentCols = "";
            string parentConds = "";
            for (int i = 0; i < fk.ChildColumns.Count; i++)
            {
                childCols = childCols + fk.ChildColumns[i];
                if (i < (fk.ChildColumns.Count - 1))
                    childCols = childCols + ",";
            }
            for (int i = 0; i < fk.ChildConditions.Count; i++)
            {
                childConds = childConds + fk.ChildConditions[i].ToString();
                if (i < (fk.ChildConditions.Count - 1))
                    childConds = childConds + ",";
            }
            for (int i = 0; i < fk.ParentColumns.Count; i++)
            {
                parentCols = parentCols + fk.ParentColumns[i];
                if (i < (fk.ParentColumns.Count - 1))
                    parentCols = parentCols + ",";
            }
            for (int i = 0; i < fk.ParentConditions.Count; i++)
            {
                parentConds = parentConds + fk.ParentConditions[i].ToString();
                if (i < (fk.ParentConditions.Count - 1))
                    parentConds = parentConds + ",";
            }
            fke.Add(new XAttribute("childTable", fk.ChildTable), new XAttribute("childColumns", childCols), new XAttribute("childConditions", childConds),
                new XAttribute("parentTable", fk.ParentTable), new XAttribute("parentColumns", parentCols), new XAttribute("parentConditions", parentConds));
            return fke;
        }

        private void writeDDL(XElement xe)
        {
            xml.Element("DDLs").Add(xe);
        }

        public Transaction CreateSchemaUpdateTransaction()
        {
            if (usingDBSchema)
            {
                var versions = xml.Element("DDLs").Elements("version");
                var newestVersion = Convert.ToInt32(versions.Last().Value);
                if (dbVersion < newestVersion)
                {

                    XDocument changes = new XDocument(new XElement("DDLs"));
                    XElement node = (XElement)xml.Element("DDLs").Element("version");
                    int i = 0;
                    for (i = 0; i < versions.Count(); i++)
                    {
                        while (node.Name != "version")
                        {
                            node = (XElement)node.NextNode;
                        }
                        if (node.Value.Equals(Convert.ToString(dbVersion)))
                        {
                            changes.Element("DDLs").Add(node.ElementsAfterSelf());
                            break;
                        }
                        node = (XElement)node.NextNode;
                    }
                    node = (XElement)changes.Element("DDLs").Element("version");
                    for (int j = 0; j < versions.Count() - i; j++)
                    {
                        while (node.Name != "version")
                        {
                            node = (XElement)node.NextNode;
                        }
                        if (j == (versions.Count() - i) - 2)
                        {
                            changes = new XDocument(new XElement("DDLs", node.ElementsBeforeSelf()));
                            break;
                        }
                        node = (XElement)node.NextNode;
                    }

                    dbSchema.Element("DDLs").Add(changes.Element("DDLs").Elements());
                    DBConnection.ExecuteQuery("insert into dbversion (version,xmlschema) values ( " + newestVersion + ", '" + dbSchema + "')");
                    return TransactionFromXmlChanges(dbSchema.Element("DDLs").Elements());
                }
                else
                    return TransactionFromXmlChanges(dbSchema.Element("DDLs").Elements());
            }
            else
                return TransactionFromXmlChanges();
        }

       private Transaction TransactionFromXmlChanges()
        {
            return TransactionFromXmlChanges(xml.Element("DDLs").Elements());
        }

        private Transaction TransactionFromXmlChanges(IEnumerable<XElement> elements)
        {
            Transaction t = new Transaction();
            foreach (var element in elements)
            {
                switch(element.Name.ToString()){
                    case "table":
                        switch (element.Attribute("type").Value)
                        {
                            case "add":
                                t.Add(new Command(this.AddTableFromXElement(element)));
                                break;
                            case "rename":
                                t.Add(new Command(new AlterTableRename(element.Attribute("old").Value, element.Attribute("new").Value)));
                                break;
                            case "delete":
                                t.Add(new Command( new DropTable(element.Attribute("name").Value)));
                                break;
                        }
                        break;
                    case "column":
                        switch (element.Attribute("type").Value)
                        {
                            case "add":
                                t.Add(new Command(new AddColumn(element.Attribute("table").Value, element.Attribute("name").Value, Domain.Domain.FromXElement(element.Element("Domain")))));
                                break;
                            case "rename":
                                t.Add(new Command(new AlterColumnRename(element.Attribute("table").Value, element.Attribute("old").Value, element.Attribute("new").Value)));
                                break;
                            case "delete":
                                t.Add(new Command(new DropColumn(element.Attribute("table").Value, element.Attribute("name").Value)));
                                break;
                        }
                        break;
                    case "element":
                        switch (element.Attribute("type").Value)
                        {
                            case "add":
                                t.Add(new Command(new AddElement(element.Attribute("table").Value, element.Attribute("column").Value, element.Attribute("value").Value)));
                                break;
                            case "rename":
                                t.Add(new Command(new AlterElementRename(element.Attribute("table").Value, element.Attribute("column").Value, element.Attribute("old").Value, element.Attribute("new").Value)));
                                break;
                            case "delete":
                                t.Add(new Command(new DropElement(element.Attribute("table").Value, element.Attribute("column").Value, element.Attribute("value").Value)));
                                break;
                        }
                        break;
                    case "fk":
                        switch (element.Attribute("type").Value)
                        {
                            case "add":
                                t.Add(new Command(AddFKFromXElement(element)));
                            break;
                        }
                        break;
                }

            }
            return t;
        }
        private AddTable AddTableFromXElement(XElement e)
        {
            AddTable at = new AddTable(e.Attribute("name").Value);
            if (e.HasElements)
            {
                var elements = e.Elements();
                foreach (var element in elements)
                {
                    if (element.Attribute("key").Value == "True")
                        at.AddColumn(element.Attribute("name").Value, Domain.Domain.FromXElement(element.Element("Domain")), true);
                    else
                        at.AddColumn(element.Attribute("name").Value, Domain.Domain.FromXElement(element.Element("Domain")), false);
                }
            }
            return at;
        }
        private ForeignKey AddFKFromXElement(XElement e)
        {
            ForeignKey fk = new ForeignKey(e.Attribute("childTable").Value, e.Attribute("parentTable").Value);
            List<string> childCols = new List<string>();
            string xmlChildCols = e.Attribute("childColumns").Value;
            List<string> parentCols = new List<string>();
            string xmlParentCols = e.Attribute("parentColumns").Value;
            while (xmlChildCols.Contains(','))
            {
                childCols.Add(xmlChildCols.Substring(0, xmlChildCols.IndexOf(',')));
                xmlChildCols = xmlChildCols.Substring(xmlChildCols.IndexOf(',') + 2);
            }
            if (xmlChildCols != "")
                childCols.Add(xmlChildCols);
            while (xmlParentCols.Contains(','))
            {
                parentCols.Add(xmlParentCols.Substring(0, xmlParentCols.IndexOf(',')));
                xmlParentCols = xmlParentCols.Substring(xmlParentCols.IndexOf(',') + 2);
            }
            if (xmlParentCols != "")
                parentCols.Add(xmlParentCols);
            fk.ChildColumns = childCols;
            fk.ParentColumns = parentCols;
            return fk;
        }
    }
}
